// -*- c -*-
//
// %CopyrightBegin%
//
// Copyright Ericsson AB 2017-2021. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// %CopyrightEnd%
//

i_select_val_bins := select_val_bins.fetch.select;

select_val_bins.head() {
    Eterm select_val;
}

select_val_bins.fetch(Src) {
    select_val = $Src;
}

select_val_bins.select(Fail, NumElements) {
    struct Singleton {
        BeamInstr val;
    };
    struct Singleton* low;
    struct Singleton* high;
    struct Singleton* mid;
    int bdiff; /* int not long because the arrays aren't that large */

    low = (struct Singleton *) ($NEXT_INSTRUCTION);
    high = low + $NumElements;

    /* The pointer subtraction (high-low) below must produce
     * a signed result, because high could be < low. That
     * requires the compiler to insert quite a bit of code.
     *
     * However, high will be > low so the result will be
     * positive. We can use that knowledge to optimise the
     * entire sequence, from the initial comparison to the
     * computation of mid.
     *
     * -- Mikael Pettersson, Acumem AB
     *
     * Original loop control code:
     *
     * while (low < high) {
     *    mid = low + (high-low) / 2;
     *
     */
    while ((bdiff = (int)((char*)high - (char*)low)) > 0) {
        unsigned int boffset = ((unsigned int)bdiff >> 1) & ~(sizeof(struct Singleton)-1);

        mid = (struct Singleton*)((char*)low + boffset);
        if (select_val < mid->val) {
            high = mid;
        } else if (select_val > mid->val) {
            low = mid + 1;
        } else {
            Sint32* jump_tab = (Sint32 *) ($NEXT_INSTRUCTION + $NumElements);
            Sint32 offset = jump_tab[mid - (struct Singleton *)($NEXT_INSTRUCTION)];
            $JUMP(offset);
        }
    }
    $JUMP($Fail);
}

i_select_tuple_arity2 := select_val2.src.get_arity.execute;
i_select_val2 := select_val2.src.execute;

select_val2.head() {
    Eterm select_val2;
}

select_val2.src(Src) {
    select_val2 = $Src;
}

select_val2.get_arity() {
    if (ERTS_LIKELY(is_tuple(select_val2))) {
        select_val2 = *tuple_val(select_val2);
    } else {
        select_val2 = NIL;
    }
}

select_val2.execute(Fail, T1, T2) {
    Sint32* jump_tab = (Sint32 *) ($NEXT_INSTRUCTION);

    if (select_val2 == $T1) {
        $JUMP(jump_tab[0]);
    } else if (select_val2 == $T2) {
        $JUMP(jump_tab[1]);
    } else {
        $FAIL($Fail);
    }
}

i_select_tuple_arity := select_val_lin.fetch.get_arity.execute;
i_select_val_lins := select_val_lin.fetch.execute;

select_val_lin.head() {
    Eterm select_val;
}

select_val_lin.fetch(Src) {
    select_val = $Src;
}

select_val_lin.get_arity() {
    if (ERTS_LIKELY(is_tuple(select_val))) {
        select_val = *tuple_val(select_val);
    } else {
        select_val = NIL;
    }
}

select_val_lin.execute(Fail, N) {
    const BeamInstr* vs = $NEXT_INSTRUCTION;
    int ix = 0;

    for (;;) {
        if (vs[ix+0] >= select_val) {
            ix += 0;
            break;
        }
        if (vs[ix+1] >= select_val) {
            ix += 1;
            break;
        }
        ix += 2;
    }

    if (vs[ix] == select_val) {
        Sint32* jump_tab = (Sint32 *) ($NEXT_INSTRUCTION + $N);
        Eterm offset = jump_tab[ix];
        $JUMP(offset);
    } else {
        $JUMP($Fail);
    }
}

JUMP_ON_VAL(Fail, Index, N, Base) {
    if (is_small($Index)) {
        $Index = (Uint) (signed_val($Index) - $Base);
        if ($Index < $N) {
            Sint32* jump_tab = (Sint32 *) ($NEXT_INSTRUCTION);
            $JUMP(jump_tab[$Index]);
        }
    }
    $FAIL($Fail);
}

i_jump_on_val_zero := jump_on_val_zero.fetch.execute;

jump_on_val_zero.head() {
    Eterm index;
}

jump_on_val_zero.fetch(Src) {
    index = $Src;
}

jump_on_val_zero.execute(Fail, N) {
    $JUMP_ON_VAL($Fail, index, $N, 0);
}

i_jump_on_val := jump_on_val.fetch.execute;

jump_on_val.head() {
    Eterm index;
}

jump_on_val.fetch(Src) {
    index = $Src;
}

jump_on_val.execute(Fail, N, Base) {
    $JUMP_ON_VAL($Fail, index, $N, $Base);
}
